/*
 * Copyright 2012 Daniel Kurka
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.googlecode.mgwt.expenses.client.rpc;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanCodex;
import com.googlecode.mgwt.expenses.client.activities.ed.ExpenseDetailDTO;
import com.googlecode.mgwt.expenses.client.activities.el.ExpenseListDTO;
import com.googlecode.mgwt.expenses.client.rpc.autobean.Expense;
import com.googlecode.mgwt.expenses.client.rpc.autobean.Expenses;
import com.googlecode.mgwt.expenses.client.rpc.autobean.ExpensesFactory;
import com.googlecode.mgwt.storage.client.Storage;

public class ExpenseServiceLocalStorageImpl implements ExpenseDataService {

	private static final int DELAY = 200;

	private static final String LOCAL_STORAGE_KEY = "expenses";

	private final Storage storage;

	private boolean loaded;

	private final ExpensesFactory factory;

	private List<ExpenseListDTO> expenses;

	private AutoBean<Expenses> expensesBean;

	@Inject
	public ExpenseServiceLocalStorageImpl(Storage storage, ExpensesFactory factory) {
		this.storage = storage;
		this.factory = factory;
		this.loaded = false;

	}

	@Override
	public void loadExpenseList(final AsyncCallback<List<ExpenseListDTO>> callback) {
		if (!ensureLoaded(callback))
			return;
		callback.onSuccess(expenses);
	}

	@Override
	public void loadExpenseDetail(String id, final AsyncCallback<ExpenseDetailDTO> callback) {

		if (!ensureLoaded(callback))
			return;

		Map<String, Expense> map = expensesBean.as().getExpenses();
		Expense expense = map.get(id);

		if (expense == null) {
			callback.onFailure(new Exception());
			return;
		}

		AutoBean<ExpenseDetailDTO> dto = factory.createExpenseDetailDTO();
		final ExpenseDetailDTO as = dto.as();
		as.setAmount(expense.getAmount());
		as.setDate(expense.getDate());
		as.setDescription(expense.getDescription());
		as.setFotoUrl(expense.getFotoUrl());
		as.setTitle(expense.getTitle());
		as.setId(expense.getId());

		new Timer() {

			@Override
			public void run() {
				callback.onSuccess(as);

			}

		}.schedule(DELAY);

	}

	@Override
	public void updateExpense(ExpenseDetailDTO currentModel, final AsyncCallback<Boolean> callback) {
		if (!ensureLoaded(callback))
			return;

		Map<String, Expense> map = expensesBean.as().getExpenses();
		Expense expense = map.get(currentModel.getId());

		if (expense == null) {
			callback.onFailure(new Exception());
			return;
		}

		expense.setAmount(currentModel.getAmount());
		expense.setDate(currentModel.getDate());
		expense.setDescription(currentModel.getDescription());
		expense.setFotoUrl(currentModel.getFotoUrl());
		expense.setTitle(currentModel.getTitle());

		try {
			store();
		} catch (Exception e) {

			callback.onFailure(e);
			return;
		}

		// update cache..
		expenses = buildDTOList(expensesBean.as());

		new Timer() {

			@Override
			public void run() {
				callback.onSuccess(true);

			}

		}.schedule(DELAY);

	}

	private boolean ensureLoaded(AsyncCallback<?> callback) {
		if (!loaded) {
			try {
				load();
				loaded = true;
				return true;
			} catch (Exception e) {

				callback.onFailure(e);
				return false;
			}

		}
		return true;
	}

	private void store() {
		String payload = AutoBeanCodex.encode(expensesBean).getPayload();
		storage.setItem(LOCAL_STORAGE_KEY, payload);
	}

	@Override
	public void createExpense(final AsyncCallback<String> callback) {
		if (!ensureLoaded(callback))
			return;

		Map<String, Expense> map = expensesBean.as().getExpenses();
		// never in production :)
		final String id = createId(map);

		// TODO store!!
		AutoBean<Expense> expense = factory.createExpense();
		expense.as().setId(id);
		expense.as().setDate(new Date());
		expense.as().setAmount(0);
		expense.as().setDescription("");
		expense.as().setTitle("Title");
		map.put(id, expense.as());

		store();
		// update cache..
		expenses = buildDTOList(expensesBean.as());

		new Timer() {

			@Override
			public void run() {
				callback.onSuccess(id);

			}

		}.schedule(DELAY);

	}

	private String createId(Map<String, Expense> map) {
		String id;
		while (true) {
			id = "" + System.currentTimeMillis();
			if (!map.containsKey(id)) {
				break;
			}
		}
		return id;
	}

	private void load() {
		expensesBean = loadExpensesFromLocalStorage();
		expenses = buildDTOList(expensesBean.as());
		loaded = true;
	}

	private List<ExpenseListDTO> buildDTOList(Expenses expenses) {
		List<ExpenseListDTO> list = new ArrayList<ExpenseListDTO>();

		Map<String, Expense> map = expenses.getExpenses();

		Collection<Expense> values = map.values();

		for (Expense expense : values) {
			AutoBean<ExpenseListDTO> listDTO = factory.createExpenseListDTO();
			ExpenseListDTO as = listDTO.as();
			as.setAmount(expense.getAmount());
			as.setDate(expense.getDate());
			as.setTitle(expense.getTitle());
			as.setId(expense.getId());
			listDTO.setFrozen(true);
			list.add(as);
		}

		Collections.sort(list, new Comparator<ExpenseListDTO>() {

			@Override
			public int compare(ExpenseListDTO o1, ExpenseListDTO o2) {
				return o1.getDate().getTime() > o2.getDate().getTime() ? -1 : 1;

			}
		});

		return list;
	}

	private AutoBean<Expenses> loadExpensesFromLocalStorage() {
		String item = storage.getItem(LOCAL_STORAGE_KEY);

		if (item == null || "".equals(item)) {
			AutoBean<Expenses> autoBean = factory.createExpenses();
			autoBean.as().setExpenses(new HashMap<String, Expense>());
			return autoBean;
		}

		AutoBean<Expenses> bean = AutoBeanCodex.decode(factory, Expenses.class, item);

		return bean;
	}

}
